#!/bin/bash

ANBOX=anbox
SESSIONMANAGER="$ANBOX session-manager"
LAUNCHER="$ANBOX launch"
BINDMOUNTDIR=$HOME/anbox-data
SOCKETDIR=$XDG_RUNTIME_DIR/anbox
ROBOXLOGDIR=/tmp/robox
ROBOXALLLOG="" # Populated dynamically
SOCKETS="anbox_bridge qemu_pipe anbox_audio"
INPUTS="event0  event1"
ANBOX_LOG_LEVEL="debug"
EGL_LOG_LEVEL="debug"
DOCKERNAME=""
SESSIONMANAGERPID=""
FRAMEBUFFERPID=""
START=""
STOP=""
LAUNCHER=""
VNC="true"
instance=0
instance=""
EXECUTIONS=0
FAILED=255
SUPPORTEDINSTANCES=253 # Restricted by Class C networking (!1 && !255)
                       # Remember Robox IP addresses start at <MASK>.2
DISPLAY=:1

trap die INT

function warning() {
    echo -e "[$(date +%T)] \e[01;31m$instancenum: $@ \e[0m" >> $ROBOXALLLOG
    echo -e "\e[01;31m$instancenum: $@ \e[0m"
}

function out() {
    echo -e "[$(date +%T)] $instancenum: $@" >> $ROBOXALLLOG
    if [[ "$QUIET" == "true" ]]; then return; fi
    echo -e "$instancenum: $@"
}

function debug() {
    echo -e "[$(date +%T)] $instancenum: $@" >> $ROBOXALLLOG
    if [[ "$DEBUG" != "true" ]]; then return; fi
    echo -e "$instancenum: $@"
}

function die() {
    opts=""

    if [[ "$DEBUG" == "true" ]]; then
	opts="$opts -d"
    fi

    if [[ "$QUIET" == "true" ]]; then
	opts="$opts -q"
    fi

    if [[ "$VNC" == "true" ]]; then
	opts="$opts -v"
    fi

    EXECUTIONS=$(($EXECUTIONS+1))

    out "Stopping"
    $0 $opts stop $instancenum -f -lf $ROBOXALLLOG -x $EXECUTIONS

    $0 $opts start $instancenum -lf $ROBOXALLLOG -x $EXECUTIONS

    return $instancenum
}

function start_binder_ashmem()
{
    BINDERNODE=/dev/binder$instancenum
    ASHMEMNODE=/dev/ashmem

    if [[ ! -x $(which dkms) ]]; then
	out "DKMS isn't installed.  Please install it then restart '$(basename $0)'"
	return $FAILED
    fi

    if [[ ! -c $BINDERNODE ]]; then

	if [[ -c /dev/binder ]]; then
	    out "Binder module loaded without sufficient device(s) support"
	    return $FAILED
	fi

	# Is the Binder module installed?  If not, try to install it
	grep -r binder_linux /lib/modules/`uname -r` > /dev/null
	if [[ $? -ne 0 ]]; then
	    if [[ ! -d /usr/src/linux-headers-`uname -r` ]]; then
		warning "Kernel headers not installed -- can not build Binder module"
		return $FAILED
	    fi

	    out "Installing Binder module via DKMS"
	    sudo dkms install $PWD/kernel/binder > /dev/null

	    if [[ $? -ne 0 ]]; then
		warning "Failed to install Binder module, exiting"
		return $FAILED
	    fi
	fi

	# Create a Binder devnode for each instance
	# +1 becuase Binder's numbering starts at 0 - Robox starts at 1
	sudo modprobe binder_linux num_devices=$(($SUPPORTEDINSTANCES+1))
	if [[ $? -ne 0 ]]; then
	    warning "Failed to load Binder module, exiting"
	    return $FAILED
	fi
    else
	debug "$BINDERNODE appears to be present"
    fi

    if [[ ! -c $ASHMEMNODE ]]; then

	# Is the Ashmem module installed?  If not, try to install it
	grep -r ashmem_linux /lib/modules/`uname -r` > /dev/null
	if [[ $? -ne 0 ]]; then
	    if [[ ! -d /usr/src/linux-headers-`uname -r` ]]; then
		warning "Kernel headers not installed -- can not build Ashmem module"
		return $FAILED
	    fi

	    out "Installing Ashmem module via DKMS"
	    sudo dkms install $PWD/kernel/ashmem > /dev/null

	    if [[ $? -ne 0 ]]; then
		warning "Failed to install Ashmem module, exiting"
		return $FAILED
	    fi
	fi

	# Create a Ashmem devnode for each instance
	sudo modprobe ashmem_linux
	if [[ $? -ne 0 ]]; then
	    warning "Failed to load Ashmem module, exiting"
	    return $FAILED
	fi
    else
	debug "$ASHMEMNODE appears to be present"
    fi

    debug "Ensuring $BINDERNODE and $ASHMEMNODE are useable"
    sudo chmod 777 $BINDERNODE
    sudo chmod 777 $ASHMEMNODE
}

function start_framebuffer()
{
    if [[ "$VNC" != "true" ]]; then
	# This function is only relevant for VNC
	return
    fi

    out "$instancenum: STARTING Frame Buffer"
    cmd="Xorg $display -config /etc/X11/xorg0.conf -sharevts"

    # LEE: Might need a sleep here
    ps aux | grep Xorg | grep "Xorg[[:space:]]*$display " > /dev/null
    if [ $? -gt 0 ]; then
        warning "\e[01;31m  Create Xorg service\e[0m"
        debug $cmd
        eval $cmd &
        sleep 3
    else
        out "The Xorg service has been created \e[0m"
    fi
}

function start_session_manager()
{
    out "STARTING Session Manager"

    if [[ "$VNC" != "true" ]]; then
	# Use the default "freeform"
	windowing=""
    else
	windowing="--single-window"
    fi

    cmd="taskset -c 4-7 $SESSIONMANAGER --run-multiple=$instancenum --standalone --experimental $windowing --window-size=720,1280"

    echo $cmd
    export DISPLAY=$DISPLAY
    export EGL_PLATFORM=x11
    export EGL_LOG_LEVEL="fatal"
    eval $cmd &
    SESSIONMANAGERPID=$!

    disown

    TIMEOUT=0
    while true; do
	ps -h $SESSIONMANAGERPID > /dev/null
	if [[ $? -gt 0 ]]; then
	    if [[ $TIMEOUT -gt 2 ]]; then
		warning "FAILED to start the Session Manager"
		return $FAILED
	    else
		TIMEOUT=$(($TIMEOUT+1))
	    fi
	    sleep 2
	else
	    break
	fi
    done
}

function configure_networking()
{
    #unique_ip=$instancenum
    unique_ip=`ps -ef | grep proxy | grep 172.17.0 | awk '{print $16}' | awk -F "." '{print $4}' | sort -nur | sed -n '1p'`
    if [[ ! $unique_ip ]]; then
        unique_ip=1
    fi
    unique_ip=$(($unique_ip + 1))
    final_ip=172.17.0.$unique_ip

    out "CREATING network configuration (using $final_ip)"

    mkdir -p $BINDMOUNTDIR/$instancenum/data/misc/ethernet

    $ANBOX generate-ip-config --ip=$final_ip --gateway=172.17.0.1
    if [[ $? -ne 0 ]]; then
	warning "FAILED to configure Networking"
	return $FAILED
    fi

    cp ipconfig.txt $BINDMOUNTDIR/$instancenum/data/misc/ethernet
}

function start_docker_container()
{
    out "STARTING Docker container"
    DOCKERNAME=$instancenum

    TIMEOUT=0
    while true; do
	if [[ -S $SOCKETDIR/$instancenum/sockets/qemu_pipe ]] &&
	       [[ -S $SOCKETDIR/$instancenum/sockets/anbox_audio ]] &&
	       [[ -S $SOCKETDIR/$instancenum/sockets/anbox_bridge ]] &&
	       [[ -S $SOCKETDIR/$instancenum/input/event0 ]] &&
	       [[ -S $SOCKETDIR/$instancenum/input/event1 ]]; then
	    break
	else
	    if [[ $TIMEOUT -gt 15 ]]; then
		warning "FAILED: Timed out waiting for sockets"
		return $FAILED
	    else
		debug "Not all sockets are present - ZZzzz!"
		sleep 2
		TIMEOUT=$(($TIMEOUT+1))
	    fi
	fi
    done

    adbport=`expr 2 \* $instancenum`
    adbport=$(($adbport + 5557))
    port=$(($instancenum + 8000))
    cmd="docker run -d -it \
	   --cap-add=SYS_ADMIN \
	   --cap-add=NET_ADMIN \
	   --cap-add=SYS_MODULE \
	   --cap-add=SYS_NICE \
	   --cap-add=SYS_TIME \
	   --cap-add=SYS_TTY_CONFIG \
	   --cap-add=NET_BROADCAST \
	   --cap-add=IPC_LOCK \
	   --cap-add=SYS_RESOURCE \
           --security-opt="apparmor=unconfined" \
           --security-opt="seccomp=unconfined" \
	   --cpuset-cpus="4-7" \
	   --cpuset-mems="0" \
	   --memory="3584M" \
	   --name instance$DOCKERNAME \
	   -e PATH=/system/bin:/system/xbin \
	   --device=dev/binder$instancenum:/dev/binder:rwm \
	   --device=/dev/ashmem:/dev/ashmem:rwm \
           --device=/dev/fuse:/dev/fuse:rwm \
	   --volume=$SOCKETDIR/$instancenum/sockets/qemu_pipe:/dev/qemu_pipe \
	   --volume=$SOCKETDIR/$instancenum/sockets/anbox_audio:/dev/anbox_audio:rw \
	   --volume=$SOCKETDIR/$instancenum/sockets/anbox_bridge:/dev/anbox_bridge:rw \
	   --volume=$SOCKETDIR/$instancenum/input/event0:/dev/input/event0:rw \
	   --volume=$SOCKETDIR/$instancenum/input/event1:/dev/input/event1:rw \
	   --volume=$BINDMOUNTDIR/$instancenum/cache:/cache:rw \
	   --volume=$BINDMOUNTDIR/$instancenum/data:/data:rw \
	   -p $adbport:5555 \
           android:robox_317 /anbox-init.sh"
           #android:robox_lou /anbox-init.sh"

    debug $cmd
    $cmd &>> $ROBOXALLLOG $DK
    if [[ $? -ne 0 ]]; then
	warning "FAILED to start the Docker Container"
	return $FAILED
    fi
}

function start_launcher()
{
    cmd="$LAUNCHER --package=org.anbox.appmgr \
--component=org.anbox.appmgr.AppViewActivity \
--run-multiple=$instancenum"

    if [[ "$LAUNCHER" != "true" ]]; then
	return
    fi

    out "STARTING Launcher"
    debug $cmd
    $cmd

    if [[ $? -ne 0 ]]; then
	warning "FAILED to start the Launcher"
	return $FAILED
    fi
}

function start_vnc_server()
{
    if [[ "$VNC" != "true" ]]; then
	# This function is only relevant for VNC
	return
    fi

    PASSWORD=robox$RANDOM

    # WARNING: The passwd method should only be used for testing/demos

    out "STARTING VNC Server"
    cmd="x11vnc -display $DISPLAY -N -forever -shared -reopen -passwd $PASSWORD -desktop $instancenum -bg"

    debug $cmd

    # VNC is too noisy to have in the debug log
    $cmd -q &> /dev/null

    if [[ $? -ne 0 ]]; then
	warning "FAILED to start the VNC Server"
	return $FAILED
    fi

    out "PASSWORD=\e[1m\e[34m$PASSWORD\e[0m"
}

function start()
{
    ps aux | grep -v grep | grep "$instance \|$instance$" > /dev/null
    if [[ $? -eq 0 ]]; then
	OUT=`ps aux | grep -v grep | grep "$instance \|$instance$"`
	out $OUT
	warning "$instance is already running -- please stop it before continuing"
	return $FAILED
    fi

    docker network inspect bridge | grep \"$instance\" > /dev/null
    if [[ $? -eq 0 ]]; then
	docker network disconnect -f bridge $instance
    fi

    # Export current log level values
    export ANBOX_LOG_LEVEL=$ANBOX_LOG_LEVEL
    export EGL_LOG_LEVEL=$EGL_LOG_LEVEL

    # Raise system resource limits - required for many containers
    sudo sysctl -w fs.inotify.max_user_instances=8192 > /dev/null
    sudo sysctl -w fs.file-max=1000000 > /dev/null
    sudo sysctl -w kernel.shmmni=24576 > /dev/null
    sudo sysctl -w kernel.pid_max=200000 > /dev/null

    ulimit -n 4096
    ulimit -s unlimited

    # Enable core dumps
    ulimit -c unlimited

    start_framebuffer
    if [[ $? -eq $FAILED ]]; then
	die
	return $FAILED
    fi

    start_session_manager
    if [[ $? -eq $FAILED ]]; then
	die
	return $FAILED
    fi

    configure_networking
    if [[ $? -eq $FAILED ]]; then
	die
	return $FAILED
    fi

    debug "Ensuring all sockets are useable"
    sudo chmod -R 777 $XDG_RUNTIME_DIR/anbox

    start_docker_container
    if [[ $? -eq $FAILED ]]; then
	die
	return $FAILED
    fi

    start_launcher
    if [[ $? -eq $FAILED ]]; then
	die
	return $FAILED
    fi
}

function stop()
{
    if [[ "$FORCE" == "" ]]; then
	ps aux | grep -v grep | grep "run-multiple=$instancenum" > /dev/null
	if [[ $? -ne 0 ]]; then
	    out "Nothing to do"

	    # Remove possible remnent files anyway, just in case
	    sudo rm -rf $XDG_RUNTIME_DIR/anbox/$instancenum > /dev/null
	    sudo rm -rf $BINDMOUNTDIR/$instancenum > /dev/null

	    exit 0
	fi
    fi

    # Stop Docker
    docker ps -a | grep $instance$ > /dev/null
    if [[ $? -eq 0 ]]; then
	out "STOPPING Docker"
	docker stop $instance &>> $ROBOXALLLOG &
	DOCKERSTOPPID=$!
	disown

	while true; do
	    if [[ -d /proc/$DOCKERSTOPPID ]]; then
		if [[ $TIMEOUT -gt 48 ]]; then
		    out "Docker Stop locked up - trying again"
		    kill $DOCKERSTOPPID &>> $ROBOXALLLOG
		    die
		    return $FAILED
		else
		    TIMEOUT=$(($TIMEOUT+1))
		fi
		sleep 5
	    else
		break
	    fi
	done

	debug "REMOVING Docker"
	docker rm -f $instance &>> $ROBOXALLLOG
	if [[ $? -ne 0 ]]; then
	    warning "FAILED to remove Docker container"
	fi
    else
	out "NOT stopping Docker, it's not running"
    fi

    # Stop Session Manager
    PID=$(ps aux | grep session-manager | grep "run-multiple=$instancenum" | column -t | cut -d$' ' -f3)
    echo $PID
    if [[ "$PID" != "" ]]; then
	out "STOPPING Session Manager ($PID)"
	if [[ "$PERF" == "true" ]]; then
	    kill -INT $PID
	else
	    kill -9 $PID
	fi
    else
	out "NOT stopping Session Manager, it's not running"
    fi

    sudo rm -rf $XDG_RUNTIME_DIR/anbox/$instancenum
    sudo rm -rf $BINDMOUNTDIR/$instancenum


    rm -f /tmp/.X$instancenum-lock

    # Remove unattached shared memory (VNC does not free it properly)
    IDS=`ipcs -m | grep '^0x' | grep $USER | awk '{print $2, $6}' | grep ' 0$' | awk '{print $1}'`
    for id in $IDS; do
	ipcrm shm $id &> /dev/null
    done
}

parse_args()
{
    if [[ $# -lt 2 ]] || [[ $# -gt 11 ]]; then
	warning "Too many arguments"
	return $FAILED
    fi

    while [[ $# -gt 0 ]]; do
	case $1 in
	    start|--start)
		START=true
		instancenum=$2
		shift
		;;
	    stop|--start)
		STOP=true
		instancenum=$2
		shift
		;;
	    -v|--vnc) # Default
		VNC=true
		;;
	    -nv|--novnc)
		VNC=""
		;;
	    -l|--launcher)
		LAUNCHER=true
		;;
	    -nl|--nolauncher) # Default
		LAUNCHER=""
		;;
	    -lf|--logfile)
		ROBOXALLLOG=$2
		shift
		;;
	    -d|--debug)
		ANBOX_LOG_LEVEL=debug
		EGL_LOG_LEVEL=debug
		DEBUG=true
		;;
	    -q|--quiet)
		QUIET=true
		;;
	    -t|--trace)
		ANBOX_LOG_LEVEL=trace
		;;
	    -f|--force) # To be used with 'stop'
		FORCE=true
		;;
	    -x|--executions) # Internal flag - do not use manually
		EXECUTIONS=$2
		shift
		;;
	    *)
		warning "Unrecognised parameter $1"
		return $FAILED
		;;
	esac
	shift
    done
}

main ()
{
    parse_args $@
    if [[ $? -ne 0 ]]; then
	return $FAILED
    fi

    if [[ "$ROBOXALLLOG" == "" ]]; then
	ROBOXALLLOG=$ROBOXLOGDIR/$instancenum/$(date +%F-%H%p)
	mkdir -p $ROBOXLOGDIR/$instancenum
    fi

    if [[ $EXECUTIONS -ge 5 ]]; then
	# BUG: https://github.com/moby/moby/issues/22312
	warning "Too many stop loops (try restarting dockerd) giving up!"
	return $FAILED
    fi	

    if [[ $instancenum -gt $SUPPORTEDINSTANCES ]] || [[ $instancenum -lt 1 ]]; then
	warning "Instance should be between 1 and $SUPPORTEDINSTANCES ($instancenum)"
	return $FAILED
    fi
    instance=instance$instancenum

    if [[ "$QUIET" == "true" ]] && [[ "$DEBUG" == "true" ]]; then
	warning "Debug and Quiet modes are mutually exclusive"
	return $FAILED
    fi

    # Ensure we have current sudo privileges
    if [[ $EUID -ne 0 ]]; then
	sudo ls > /dev/null
    fi

    if [[ "$START" == "true" ]]; then
	if [[ "$VNC" != "true" ]]; then
	    if [[ "$DISPLAY" == "" ]]; then
		warning "Display is not set and VNC not selected - you have 3 options:"
		warning " 1. Run from a terminal on a desktop environment"
		warning " 2. Use the [-v|--vnc] flag to start a VNC session to connect to"
		warning " 3. Manually set the DISPLAY environment variable to the correct value"
		warning "Exiting ..."
		return $FAILED
	    fi
	fi

	out "Attempting to start instance $instance"
	start
    elif  [[ "$STOP" == "true" ]]; then
	out "Attempting to stop instance $instance"
	stop
    fi

    return $?
}

main $@
exit $?
